#include "painter.h"
#include "overlay/overlay.h"
#include "frameui/fontsys.h"
#include "twitch.h"
#include "base/checksum.h"
#include "base/file.h"
#include "mainwnd.h"
#include "app.h"

OverlayPainter::OverlayPainter()
  : client(NULL)
  , mainWnd(NULL)
  , hWndPrev(NULL)
  , data(true)
  , repaintPreview(true)
{
  memset(&params, 0, sizeof params);
  params.left = 0.7;
  params.top = 0.05;
  params.right = 0.95;
  params.bottom = 0.20;
  params.background = 0x44000000;
  wcscpy(params.fontFace, L"Helvetica");
  params.fontSize = 12;
  params.textColor = 0xFFFFFF;
  params.lockPosition = false;
  params.toggleHotkey = 0;
  params.port = 6667;
  params.replyHotkey = 0;
  File* pfile = File::open("kappaparams.cfg", File::READ);
  if (pfile)
  {
    pfile->read(&params, sizeof params);
    delete pfile;
  }
  updateParams();
}
inline void _relax(int x, int& a, int& b)
{
  if (x < a) a = x;
  if (x > b) b = x;
}
void OverlayPainter::updateParams()
{
  params.textColor &= 0xFFFFFF;
  params.fgAlpha = 255;
  params.borderColor = (params.textColor & 0xFFFFFF) | (params.background & 0xFF000000);
  params.borderWidth = 2;
  params.padding = 8;
  params.lineHeight = 18;
  int cmin, cmax;
  cmin = cmax = (params.textColor & 0xFF);
  _relax((params.textColor >> 8) & 0xFF, cmin, cmax);
  _relax((params.textColor >> 16) & 0xFF, cmin, cmax);
  _relax(params.background & 0xFF, cmin, cmax);
  _relax((params.background >> 8) & 0xFF, cmin, cmax);
  _relax((params.background >> 16) & 0xFF, cmin, cmax);
  params.nameColorMin = Image::clr(cmin, cmin, cmin);
  params.nameColorMax = Image::clr(cmax, cmax, cmax);
  if ((params.background & 0xFFFFFF) > (params.textColor & 0xFFFFFF))
    params.nameColorMax = Image::interpolatea(params.background >> 24, params.nameColorMin, params.nameColorMax);
  else
    params.nameColorMin = Image::interpolatea(params.background >> 24, params.nameColorMax, params.nameColorMin);
  params.nameColorMin &= 0xFFFFFF;
  params.nameColorMax &= 0xFFFFFF;
  params.sysColor = Image::interpolatea(0x80, params.nameColorMin, params.nameColorMax);
  params.flags = 0;
  if (SharedHeader* hdr = data.lock())
  {
    hdr->replyHotkey = params.replyHotkey;
    data.unlock();
  }
  repaint();
}
void OverlayPainter::resetPos()
{
  params.left = 0.7;
  params.top = 0.05;
  params.right = 0.95;
  params.bottom = 0.20;

  if (data.lock())
  {
    SharedHeader* hdr = data.header();
    if (hdr->hWnd)
    {
      RECT rc;
      GetClientRect(hdr->hWnd, &rc);
      if (rc.right && rc.bottom)
      {
        hdr->left = int(params.left * (double) rc.right);
        hdr->top = int(params.top * (double) rc.bottom);
        hdr->right = int(params.right * (double) rc.right);
        hdr->bottom = int(params.bottom * (double) rc.bottom);
      }
    }
    data.unlock();
  }
  repaint();
}
OverlayPainter::~OverlayPainter()
{
  File* pfile = File::open("kappaparams.cfg", File::REWRITE);
  if (pfile)
  {
    pfile->write(&params, sizeof params);
    delete pfile;
  }
}

void OverlayPainter::addMessage(int type, char const* name, char const* text)
{
  if (type == CHAT_CLEAR)
    lines.clear();
  else if (type == CHAT_CONNECTED)
  {
    if (lines.length() == 1 && lines[0].type == ChatLine::tSystem)
      lines[0].text = WideString(text);
  }
  else if (type == CHAT_CLEARUSER)
  {
    WideString wname(name);
    for (int i = 0; i < lines.length(); i++)
    {
      if ((lines[i].type == ChatLine::tMessage || lines[i].type == ChatLine::tEmote) && wname.icompare(lines[i].name) == 0)
      {
        lines[i].type |= ChatLine::tDeleted;
        lines[i].text = WideString(text);
      }
    }
  }
  else
  {
    ChatLine& line = lines.push();
    line.type = (type == CHAT_SYSTEM ? ChatLine::tSystem : ChatLine::tMessage);
    if (type == CHAT_MESSAGE)
    {
      line.name = WideString(name);
      line.name.toLower();
      if (line.name.length())
        line.name.replace(0, towupper(line.name[0]));
    }
    if (type == CHAT_MESSAGE && !strnicmp(text, "\1ACTION ", 8))
    {
      line.type = ChatLine::tEmote;
      line.text = WideString(text + 8, strlen(text) - 9);
    }
    else
      line.text = WideString(text);
    if (lock())
    {
      drawLines();
      if (lines.length() > 100)
      {
        lines.remove(0);
        data.popline(0);
      }
      drawPreview();
      unlock();
    }
    else if (lines.length() > 100)
      lines.remove(0);
    return;
  }

  repaint();
}

Image* OverlayPainter::renderText(HFONT hFont, wchar_t const* text, int maxWidth, bool isfirst, int* endptr)
{
  int end = 0;
  while (text[end] && FontSys::getTextSize(hFont, text, end + 1).cx <= maxWidth)
    end++;
  int saved = end;
  while (end > 0 && text[end] && !iswspace((unsigned short) text[end]))
    end--;
  if (end == 0 && isfirst) end = saved;

  SIZE sz = FontSys::getTextSize(hFont, text, end);
  if (endptr) *endptr = end;
  if (end == 0) return NULL;

  HDC hNullDC = GetDC(NULL);

  HDC hDC = CreateCompatibleDC(hNullDC);
  HBITMAP hBitmap = CreateCompatibleBitmap(hNullDC, sz.cx, sz.cy);
  SelectObject(hDC, hBitmap);
  RECT rc = {0, 0, sz.cx, sz.cy};
  SetBkColor(hDC, 0);
  SetTextColor(hDC, 0xFFFFFF);
  SelectObject(hDC, hFont);
  DrawText(hDC, text, end, &rc, DT_NOPREFIX);

  ReleaseDC(NULL, hNullDC);

  Image* img = new Image(sz.cx, sz.cy);
  GetBitmapBits(hBitmap, sz.cx * sz.cy * sizeof(uint32), img->bits());
  img->toAlpha();
  DeleteObject(hBitmap);
  DeleteDC(hDC);

  return img;
}
void OverlayPainter::lineAddImage(Image* dst, int y, int& x, Image const* src, uint32 color)
{
  BLTInfo blt(src, x, y - src->height());
  blt.modulate = color;
  dst->blt(blt);

  x += src->width();
}
int OverlayPainter::lineAddText(Image* dst, int y, int& x, int maxWidth, wchar_t const* text, HFONT hFont, uint32 color)
{
  int end = 0;
  Image* src = renderText(hFont, text, maxWidth - x, x == 0, &end);
  if (src)
  {
    lineAddImage(dst, y, x, src, color);
    delete src;
  }
  return end;
}
OverlayPainter::RenderBlock::RenderBlock(int w, int h, int t)
  : lineWidth(w)
  , lineHeight(h)
  , textSize(t)
{}
void OverlayPainter::blockStart(RenderBlock* blk)
{
  blk->lines.clear();
  blockNext(blk);
}
void OverlayPainter::blockNext(RenderBlock* blk)
{
  Image& img = blk->lines.push();
  img.setSize(blk->lineWidth, blk->lineHeight * 3);
  img.fill(0);
  blk->curWidth = 0;
}
void OverlayPainter::blockAddImage(RenderBlock* blk, Image const* src, int pad)
{
  if (src == NULL || src->width() > blk->lineWidth) return;
  int lpad = pad;
  if (blk->curWidth == 0) lpad = 0;
  if (blk->curWidth && blk->curWidth + src->width() + lpad > blk->lineWidth)
    blockNext(blk);
  else
    blk->curWidth += lpad;
  lineAddImage(&blk->lines.top(), 2 * blk->lineHeight + (src->height() - blk->textSize) / 2,
    blk->curWidth, src, 0xFFFFFFFF);
  blk->curWidth += pad;
}
void OverlayPainter::blockAddText(RenderBlock* blk, wchar_t const* text, HFONT hFont, uint32 color)
{
  int cur = 0;
  while (text[cur])
  {
    int add = lineAddText(&blk->lines.top(), 2 * blk->lineHeight, blk->curWidth, blk->lineWidth,
      text + cur, hFont, color);
    if (add == 0 && blk->curWidth == 0)
      cur++;
    else
    {
      cur += add;
      if (text[cur])
        blockNext(blk);
    }
  }
}
struct EmoteStringItem
{
  EmoticonImage* image;
  string text;
  EmoteStringItem()
    : image(NULL)
  {}
};
void OverlayPainter::blockAddTextEmotes(RenderBlock* blk, wchar_t const* text,
  Array<int> const* emoteset, HFONT hFont, uint32 color)
{
  Array<Emoticon>* emoticons = client->getEmoticons();
  if (emoticons == NULL)
  {
    blockAddText(blk, text, hFont, color);
    return;
  }

  Array<EmoteStringItem> items[2];
  items[0].push().text = String(text);
  int cur = 0;
  for (int i = 0; i < emoticons->length(); i++)
  {
    EmoticonImage* img = (*emoticons)[i].getImage(emoteset);
    if (img)
    {
      items[1 - cur].clear();
      for (int j = 0; j < items[cur].length(); j++)
      {
        if (items[cur][j].image)
          items[1 - cur].push(items[cur][j]);
        else
        {
          string str = items[cur][j].text;
          (*emoticons)[i].prog->GlobalReplace("\1", &str);
          int prev = 0;
          for (int p = 0; p < str.length(); p++)
          {
            if (str[p] == '\1')
            {
              if (prev < p)
                items[1 - cur].push().text = str.substr(prev, p - prev);
              items[1 - cur].push().image = img;
              prev = p + 1;
            }
          }
          if (prev < str.length())
            items[1 - cur].push().text = str.substr(prev);
        }
      }
      cur = 1 - cur;
    }
  }

  for (int i = 0; i < items[cur].length(); i++)
  {
    if (items[cur][i].image)
      blockAddImage(blk, items[cur][i].image->getImage(), 1);
    else
      blockAddText(blk, WideString(items[cur][i].text.c_str()), hFont, color);
  }
}

static void ClientToScreen(HWND hWnd, RECT* rc)
{
  ClientToScreen(hWnd, ((POINT*) rc));
  ClientToScreen(hWnd, ((POINT*) rc) + 1);
}

void OverlayPainter::update()
{
  if (!data.lock()) return;

  SharedHeader* hdr = data.header();
  DWORD id = GetWindowThreadProcessId(hdr->hWnd, NULL);
  mainWnd->setTargetThread(id);

  if (!IsWindow(hdr->hWnd))
    hdr->hWnd = NULL;
  if (hdr->hWnd == NULL)
  {
    SIZE sz;
    sz.cx = GetSystemMetrics(SM_CXSCREEN);
    sz.cy = GetSystemMetrics(SM_CYSCREEN);
    hdr->left = int(params.left * (double) sz.cx);
    hdr->top = int(params.top * (double) sz.cy);
    hdr->right = int(params.right * (double) sz.cx);
    hdr->bottom = int(params.bottom * (double) sz.cy);
  }
  else if (hdr->hWnd != hWndPrev)
  {
    RECT rc;
    GetClientRect(hdr->hWnd, &rc);
    if (rc.right && rc.bottom)
    {
      hdr->left = int(params.left * (double) rc.right);
      hdr->top = int(params.top * (double) rc.bottom);
      hdr->right = int(params.right * (double) rc.right);
      hdr->bottom = int(params.bottom * (double) rc.bottom);
    }
  }
  else
  {
    RECT rc;
    GetClientRect(hdr->hWnd, &rc);
    if (rc.right && rc.bottom)
    {
      params.left = double(hdr->left) / double(rc.right);
      params.top = double(hdr->top) / double(rc.bottom);
      params.right = double(hdr->right) / double(rc.right);
      params.bottom = double(hdr->bottom) / double(rc.bottom);
    }
  }
  if (hdr->right < hdr->left) hdr->right = hdr->left;
  if (hdr->bottom < hdr->top) hdr->bottom = hdr->top;
  hWndPrev = hdr->hWnd;

  if (data.frame()->width != hdr->right - hdr->left || data.frame()->height != hdr->bottom - hdr->top)
    repaint();
  else
  {
    ChannelInfo* info = (client ? client->getInfo() : NULL);
    if (ChannelInfo::hash(info) != titleHash)
      drawFrame();
    drawPreview();
  }

  data.unlock();
}

HWND OverlayPainter::getTargetWnd()
{
  return data.header()->hWnd;
}
bool OverlayPainter::getTargetRect(RECT* rc)
{
  SharedHeader* hdr = data.lock();
  if (!hdr) return false;
  SIZE sz = mainWnd->previewSize();
  SIZE szo;
  if (hdr->hWnd)
  {
    RECT rc;
    GetClientRect(hdr->hWnd, &rc);
    if (rc.right && rc.bottom)
    {
      szo.cx = rc.right;
      szo.cy = rc.bottom;
    }
  }
  else
  {
    szo.cx = GetSystemMetrics(SM_CXSCREEN);
    szo.cy = GetSystemMetrics(SM_CYSCREEN);
  }
  rc->left = hdr->left * sz.cx / szo.cx;
  rc->top = hdr->top * sz.cy / szo.cy;
  rc->right = hdr->right * sz.cx / szo.cx;
  rc->bottom = hdr->bottom * sz.cy / szo.cy;
  data.unlock();
  return true;
}
void OverlayPainter::updateRect()
{
  SharedHeader* hdr = data.lock();
  if (!hdr) return;
  SIZE sz;
  sz.cx = GetSystemMetrics(SM_CXSCREEN);
  sz.cy = GetSystemMetrics(SM_CYSCREEN);
  params.left = double(hdr->left) / double(sz.cx);
  params.top = double(hdr->top) / double(sz.cy);
  params.right = double(hdr->right) / double(sz.cx);
  params.bottom = double(hdr->bottom) / double(sz.cy);
  //drawPreview();
  data.unlock();
}
bool OverlayPainter::setTargetRect(RECT const* rc)
{
  SharedHeader* hdr = data.lock();
  if (!hdr) return false;
  SIZE sz = mainWnd->previewSize();
  SIZE szo;
  if (hdr->hWnd)
  {
    RECT rc;
    GetClientRect(hdr->hWnd, &rc);
    if (rc.right && rc.bottom)
    {
      szo.cx = rc.right;
      szo.cy = rc.bottom;
    }
  }
  else
  {
    szo.cx = GetSystemMetrics(SM_CXSCREEN);
    szo.cy = GetSystemMetrics(SM_CYSCREEN);
  }
  hdr->left = rc->left * szo.cx / sz.cx;
  hdr->top = rc->top * szo.cy / sz.cy;
  hdr->right = rc->right * szo.cx / sz.cx;
  hdr->bottom = rc->bottom * szo.cy / sz.cy;
  if (hdr->hWnd == NULL)
  {
    params.left = double(rc->left) / double(sz.cx);
    params.top = double(rc->top) / double(sz.cy);
    params.right = double(rc->right) / double(sz.cx);
    params.bottom = double(rc->bottom) / double(sz.cy);
  }
  drawPreview();
  data.unlock();
  return true;
}
bool OverlayPainter::lock()
{
  return (data.lock() != NULL);
}
void OverlayPainter::unlock()
{
  data.unlock();
}
int OverlayPainter::hidden()
{
  return data.header()->hidden;
}
void OverlayPainter::setHidden(int h)
{
  data.header()->hidden = h;
}

void OverlayPainter::repaint()
{
  SharedHeader* hdr = data.lock();
  if (!hdr) return;
  hdr = data.reset(hdr->right - hdr->left, hdr->bottom - hdr->top, lines.length());

  if (hdr->right > hdr->left && hdr->bottom > hdr->top)
  {
    drawFrame();
    drawLines();
    updateReply();
  }
  drawPreview();
  data.unlock();
}
void OverlayPainter::drawFrame()
{
  FrameHeader* frame = data.frame();
  if (frame->width == 0 || frame->height == 0) return;
  int oldScroll = data.header()->maxScroll + frame->height - frame->insetTop - frame->insetBottom;
  frame->insetLeft = params.borderWidth + params.padding;
  frame->insetBottom = params.borderWidth + params.padding;

  Image image(frame->width, frame->height, data.bits(frame->offset), params.background);
  if (params.borderWidth)
  {
    int bw = (params.borderWidth < frame->width ? params.borderWidth : frame->width);
    int bh = (params.borderWidth < frame->height ? params.borderWidth : frame->height);
    image.fill(params.borderColor, 0, 0, bw, frame->height);
    image.fill(params.borderColor, 0, 0, frame->width, bh);
    image.fill(params.borderColor, frame->width - bw, 0, bw, frame->height);
    image.fill(params.borderColor, 0, frame->height - bh, frame->width, bh);
  }

  int titleHeight = params.lineHeight + params.padding;
  ChannelInfo* info = (client ? client->getInfo() : NULL);
  if (info && frame->height > titleHeight + params.borderWidth)
  {
    image.fill(params.borderColor, 0, titleHeight + params.borderWidth, frame->width, 1);

    RECT titleRect = {params.borderWidth, params.borderWidth,
      frame->width - params.borderWidth, params.borderWidth + titleHeight};
    if (info->logo)
    {
      BLTInfo blt(info->logo);
      blt.x = titleRect.left;
      blt.y = titleRect.top;
      blt.dstH = titleRect.bottom - titleRect.top;
      blt.dstW = blt.dstH * info->logo->width() / info->logo->height();
      if (blt.dstW > titleRect.right - titleRect.left)
        blt.dstW = titleRect.right - titleRect.left;
      image.blt(blt);
      titleRect.left += blt.dstW;
    }

    if (info->viewers)
    {
      Image* img = renderText(FontSys::getFont(params.fontSize, params.fontFace),
        WideString::format(L"%d viewers", info->viewers),
        titleRect.right - titleRect.left - params.padding, true, NULL);
      if (img)
      {
        image.fill(params.borderColor, titleRect.right - img->width() - params.padding,
          titleRect.top, 1, titleRect.bottom - titleRect.top);
        BLTInfo blt(img);
        blt.x = titleRect.right - img->width() - params.padding / 2;
        if (blt.srcH > titleHeight) blt.srcH = titleHeight;
        blt.srcY = (img->height() - blt.srcH) / 2;
        blt.y = titleRect.top + (titleHeight - blt.srcH) / 2;
        blt.dstH = blt.srcH;
        blt.modulate = Image::clr_noflip(params.textColor, params.fgAlpha);
        image.blt(blt);
        titleRect.right -= img->width() + params.padding;
        delete img;
      }
    }

    Image* img = renderText(FontSys::getFont(params.fontSize + 2, params.fontFace),
      WideString(info->title), titleRect.right - titleRect.left - params.padding * 2, true, NULL);
    if (img)
    {
      BLTInfo blt(img);
      blt.x = titleRect.left + params.padding;
      if (blt.srcH > titleHeight) blt.srcH = titleHeight;
      blt.srcY = (img->height() - blt.srcH) / 2;
      blt.y = titleRect.top + (titleHeight - blt.srcH) / 2;
      blt.dstH = blt.srcH;
      blt.modulate = Image::clr_noflip(params.textColor, params.fgAlpha);
      image.blt(blt);
      delete img;
    }

    titleHash = ChannelInfo::hash(info);
    titleHeight++;
  }
  else
  {
    titleHash = 0;
    titleHeight = 0;
  }

  frame->insetTop = params.borderWidth + params.padding + titleHeight;
  frame->insetRight = params.borderWidth + params.padding + 8; // scroll

  frame->updated = 1;

  data.header()->maxScroll = oldScroll - frame->height + frame->insetTop + frame->insetBottom;
}
static uint32 circleColor(int dx, int dy, int r, uint32 tc, int fga)
{
  int r00 = dx * dx + dy * dy;
  int r10 = (dx + 1) * (dx + 1) + dy * dy;
  int r01 = dx * dx + (dy + 1) * (dy + 1);
  int r11 = (dx + 1) * (dx + 1) + (dy + 1) * (dy + 1);
  int rmm = (dx * dx + dx + dy * dy + dy);
  r *= r;
  r00 = (r00 < r ? fga : 0);
  r10 = (r10 < r ? fga : 0);
  r01 = (r01 < r ? fga : 0);
  r11 = (r11 < r ? fga : 0);
  rmm = (rmm < r ? fga : 0);
  int res = (r00 + r10 + r01 + r11 + 4 * rmm) / 8;
  if (res < 0) res = 0;
  if (res > fga) res = fga;
  return (res << 24) | (tc & 0xFFFFFF);
}
void OverlayPainter::drawScroll()
{
  ScrollHeader* scroll = data.scroll();
  FrameHeader* frame = data.frame();
  scroll->updated = 1;
  scroll->width = 8;
  scroll->barTop = frame->insetTop - params.padding + 1;
  scroll->barRight = params.borderWidth + 1;
  scroll->barBottom = frame->insetBottom - params.padding + 1;
  int height = frame->height - frame->insetTop - frame->insetBottom;
  if (data.header()->maxScroll <= 0 || height < 0)
    scroll->height = 0;
  else
  {
    int maxScroll = data.header()->maxScroll + height;
    scroll->height = height * height / maxScroll;
    uint32* bits = data.bits(scroll->offset);
    memset(bits, 0, sizeof(uint32) * scroll->width * scroll->height);
    if (scroll->height < scroll->width)
    {
      int cy = (scroll->height + 1) / 2;
      int cx = cy;
      for (int x = 0; x < cx; x++)
        for (int y = 0; y < scroll->height; y++)
          bits[y * scroll->width + x] = circleColor(x - cx, y - cy, cy, params.textColor, params.fgAlpha);
      for (int x = cy; x < scroll->width - cy; x++)
        for (int y = 0; y < scroll->height; y++)
          bits[y * scroll->width + x] = (params.fgAlpha << 24) | (params.textColor & 0xFFFFFF);
      cx = scroll->width - cy;
      for (int x = cx; x < scroll->width; x++)
        for (int y = 0; y < scroll->height; y++)
          bits[y * scroll->width + x] = circleColor(x - cx, y - cy, cy, params.textColor, params.fgAlpha);
    }
    else
    {
      int cx = (scroll->width + 1) / 2;
      int cy = cx;
      for (int x = 0; x < scroll->width; x++)
        for (int y = 0; y < cy; y++)
          bits[y * scroll->width + x] = circleColor(x - cx, y - cy, cx, params.textColor, params.fgAlpha);
      for (int x = 0; x < scroll->width; x++)
        for (int y = cx; y < scroll->height - cx; y++)
          bits[y * scroll->width + x] = (params.fgAlpha << 24) | (params.textColor & 0xFFFFFF);
      cy = scroll->height - cx;
      for (int x = 0; x < scroll->width; x++)
        for (int y = cy; y < scroll->height; y++)
          bits[y * scroll->width + x] = circleColor(x - cx, y - cy, cx, params.textColor, params.fgAlpha);
    }
  }
}
void OverlayPainter::drawLines()
{
  FrameHeader* frame = data.frame();
  if (frame->width == 0 || frame->height == 0) return;
  HFONT hNameFont = FontSys::getFont(params.fontSize, params.fontFace, FONT_BOLD);
  HFONT hTextFont = FontSys::getFont(params.fontSize, params.fontFace);
  uint32 textColor = Image::clr_noflip(params.textColor, params.fgAlpha);
  uint32 sysColor = Image::clr_noflip(params.sysColor, params.fgAlpha);

  RenderBlock block(frame->width - frame->insetLeft - frame->insetRight, params.lineHeight, params.fontSize);
  bool scrBottom = (data.header()->scrollPos == data.header()->maxScroll);
  while (data.header()->numLines < lines.length())
  {
    int line = data.header()->numLines;

    String userName(lines[line].name);
    UserInfo const* user = client->getUserInfo(userName);
    uint32 nameColor = Image::interpolate(user->color, params.nameColorMin, params.nameColorMax);
    nameColor = Image::clr_noflip(nameColor, params.fgAlpha);

    blockStart(&block);
    int type = (lines[line].type & 0x0F);
    if (type == ChatLine::tMessage)
    {
      //if (line > 0 && lines[line - 1].type == ChatLine::tMessage && lines[line - 1].name == lines[line].name)
      //  blockAddText(&block, L"\xBB ", hNameFont, nameColor);
      //else
      {
        for (int i = 0; i < UserInfo::sCount; i++)
        {
          if (user->special & (1 << i))
          {
            Image const* icon = client->getSpecialIcon(i);
            if (icon)
              blockAddImage(&block, icon, 2);
          }
        }
        blockAddText(&block, lines[line].name.c_str(), hNameFont, nameColor);
        blockAddText(&block, L": ", hTextFont, textColor);
      }
      blockAddTextEmotes(&block, lines[line].text.c_str(), &user->emoteset, hTextFont,
        (lines[line].type & ChatLine::tDeleted) ?
        Image::interpolate(0x6441A5, params.nameColorMin, params.nameColorMax) : textColor);
    }
    else if (type == ChatLine::tEmote)
    {
      blockAddText(&block, lines[line].name.c_str(), hNameFont, nameColor);
      blockAddText(&block, L" ", hTextFont, nameColor);
      blockAddTextEmotes(&block, lines[line].text.c_str(), &user->emoteset, hTextFont, nameColor);
    }
    else
      blockAddText(&block, lines[line].text.c_str(), hTextFont, sysColor);

    int y0 = (block.lines.length() + 1) * block.lineHeight;
    int y1 = -block.lineHeight;
    for (int i = 0; i < block.lines.length(); i++)
    {
      int left, top, right, bottom;
      if (block.lines[i].getRect(left, top, right, bottom))
      {
        int y = (i - 1) * block.lineHeight;
        if (y + top < y0) y0 = y + top;
        if (y + bottom > y1) y1 = y + bottom;
      }
    }

    if (y1 < y0) y1 = y0;
    LineHeader* lh = data.add(block.lineWidth, y1 - y0);
    if (y1 > y0)
    {
      Image image(block.lineWidth, y1 - y0, data.bits(lh->offset));
      image.fill(0);
      for (int i = 0; i < block.lines.length(); i++)
      {
        int y = (i - 1) * block.lineHeight;
        int top = 0;
        int bottom = block.lines[i].height();
        if (y < y0) top = y0 - y;
        if (y + bottom > y1) bottom = y1 - y;
        if (bottom > top)
          image.blt(0, y + top - y0, &block.lines[i], 0, top, block.lineWidth, bottom - top);
      }
    }
    lh->insetTop = y0 + 5;
    lh->insetBottom = block.lines.length() * block.lineHeight - y1;

    data.header()->maxScroll += block.lines.length() * block.lineHeight + 5;
  }
  SharedHeader* hdr = data.header();
  if (scrBottom)
    hdr->scrollPos = hdr->maxScroll;
  if (hdr->scrollPos < 0) hdr->scrollPos = 0;
  if (hdr->scrollPos > hdr->maxScroll) hdr->scrollPos = hdr->maxScroll;
  drawScroll();
}

void OverlayPainter::drawPreview()
{
  repaintPreview = true;
  if (mainWnd) mainWnd->updateImage();
}
void OverlayPainter::doDrawPreview()
{
  repaintPreview = false;
  if (mainWnd == NULL) return;
  SIZE sz = mainWnd->previewSize();
  if (sz.cx == 0 || sz.cy == 0) return;
  preview.setSize(sz.cx, sz.cy);
  for (int y = 0; y < sz.cy; y++)
    for (int x = 0; x < sz.cx; x++)
      preview.bits()[y * sz.cx + x] = ((((x / 8) + (y / 8)) & 1) ? 0xFF666666 : 0xFF999999);
  SharedHeader* hdr = data.lock();
  if (!hdr) return;

  SIZE szo;
  if (hdr->hWnd)
  {
    RECT rc;
    GetClientRect(hdr->hWnd, &rc);
    if (rc.right && rc.bottom)
    {
      szo.cx = rc.right;
      szo.cy = rc.bottom;
    }
  }
  else
  {
    szo.cx = GetSystemMetrics(SM_CXSCREEN);
    szo.cy = GetSystemMetrics(SM_CYSCREEN);
  }
  FrameHeader* frame = data.frame();
  ScrollHeader* scroll = data.scroll();
  {
    Image img(frame->width, frame->height, data.bits(frame->offset));
    BLTInfo blt(&img);
    blt.x = hdr->left * sz.cx / szo.cx;
    blt.y = hdr->top * sz.cy / szo.cy;
    blt.dstW = (hdr->right - hdr->left) * sz.cx / szo.cx;
    blt.dstH = (hdr->bottom - hdr->top) * sz.cy / szo.cy;
    preview.blt(blt);
  }
  if (scroll->width && scroll->height && hdr->maxScroll > 0)
  {
    int height = frame->height - scroll->barTop - scroll->barBottom;
    int content = frame->height - frame->insetTop - frame->insetBottom;
    int maxScroll = hdr->maxScroll + content;
    int barHeight = height * content / maxScroll;
    int barOffset = height * hdr->scrollPos / maxScroll;
    if (barOffset >= 0 && barOffset + barHeight <= height)
    {
      RECT src;
      src.left = frame->width - scroll->barRight - scroll->width;
      src.right = frame->width - scroll->barRight;
      src.top = scroll->barTop + barOffset;
      src.bottom = src.top + barHeight;
      src.left = hdr->left + src.left * (hdr->right - hdr->left) / frame->width;
      src.top = hdr->top + src.top * (hdr->bottom - hdr->top) / frame->height;
      src.right = hdr->left + src.right * (hdr->right - hdr->left) / frame->width;
      src.bottom = hdr->top + src.bottom * (hdr->bottom - hdr->top) / frame->height;
      Image img(scroll->width, scroll->height, data.bits(scroll->offset));
      BLTInfo blt(&img);
      blt.x = src.left * sz.cx / szo.cx;
      blt.y = src.top * sz.cy / szo.cy;
      blt.dstW = (src.right - src.left) * sz.cx / szo.cx;
      blt.dstH = (src.bottom - src.top) * sz.cy / szo.cy;
      preview.blt(blt);
    }
  }

  int y0 = frame->insetTop;
  int y1 = frame->height - frame->insetBottom;
  int x0 = frame->insetLeft;
  int y = y0 - hdr->scrollPos;
  for (int i = 0; i < hdr->numLines; i++)
  {
    LineHeader* line = data.line(i);
    y += line->insetTop;
    int top = 0;
    int bottom = line->height;
    if (y + top < y0) top = y0 - y;
    if (y + bottom > y1) bottom = y1 - y;
    if (bottom > top)
    {
      Image img(line->width, line->height, data.bits(line->offset));
      BLTInfo blt(&img);
      blt.srcY = top;
      blt.srcH = bottom - top;
      blt.x = (hdr->left + x0 * (hdr->right - hdr->left) / frame->width) * sz.cx / szo.cx;
      blt.y = (hdr->top + (y + top) * (hdr->bottom - hdr->top) / frame->height) * sz.cy / szo.cy;
      blt.dstW = (line->width * (hdr->right - hdr->left) / frame->width) * sz.cx / szo.cx;
      blt.dstH = ((bottom - top) * (hdr->bottom - hdr->top) / frame->height) * sz.cy / szo.cy;
      preview.blt(blt);
    }
    y += line->height + line->insetBottom;
  }

  data.unlock();
}
bool OverlayPainter::getScrollRect(RECT* rc)
{
  SharedData::Lock locker(&data);
  if (!locker.locked()) return false;
  SharedHeader* hdr = data.header();
  FrameHeader* frame = data.frame();
  ScrollHeader* scroll = data.scroll();
  SIZE sz = mainWnd->previewSize();
  SIZE szo;
  if (hdr->hWnd)
  {
    RECT rc;
    GetClientRect(hdr->hWnd, &rc);
    if (rc.right && rc.bottom)
    {
      szo.cx = rc.right;
      szo.cy = rc.bottom;
    }
  }
  else
  {
    szo.cx = GetSystemMetrics(SM_CXSCREEN);
    szo.cy = GetSystemMetrics(SM_CYSCREEN);
  }
  if (scroll->width == 0 || scroll->height == 0 || hdr->maxScroll < 0)
    return false;
  int height = frame->height - scroll->barTop - scroll->barBottom;
  int content = frame->height - frame->insetTop - frame->insetBottom;
  int maxScroll = hdr->maxScroll + content;
  int barHeight = height * content / maxScroll;
  int barOffset = height * hdr->scrollPos / maxScroll;
  if (barOffset < 0 || barOffset + barHeight > height)
    return false;
  rc->left = frame->width - scroll->barRight - scroll->width;
  rc->right = frame->width - scroll->barRight;
  rc->top = scroll->barTop + barOffset;
  rc->bottom = rc->top + barHeight;
  rc->left = hdr->left + rc->left * (hdr->right - hdr->left) / frame->width;
  rc->top = hdr->top + rc->top * (hdr->bottom - hdr->top) / frame->height;
  rc->right = hdr->left + rc->right * (hdr->right - hdr->left) / frame->width;
  rc->bottom = hdr->top + rc->bottom * (hdr->bottom - hdr->top) / frame->height;
  rc->left = rc->left * sz.cx / szo.cx;
  rc->top = rc->top * sz.cy / szo.cy;
  rc->right = rc->right * sz.cx / szo.cx;
  rc->bottom = rc->bottom * sz.cy / szo.cy;
  return true;
}
bool OverlayPainter::setScrollTop(int y)
{
  SharedData::Lock locker(&data);
  if (!locker.locked()) return false;
  SharedHeader* hdr = data.header();
  FrameHeader* frame = data.frame();
  ScrollHeader* scroll = data.scroll();
  SIZE sz = mainWnd->previewSize();
  SIZE szo;
  if (hdr->hWnd)
  {
    RECT rc;
    GetClientRect(hdr->hWnd, &rc);
    if (rc.right && rc.bottom)
    {
      szo.cx = rc.right;
      szo.cy = rc.bottom;
    }
  }
  else
  {
    szo.cx = GetSystemMetrics(SM_CXSCREEN);
    szo.cy = GetSystemMetrics(SM_CYSCREEN);
  }
  if (scroll->width == 0 || scroll->height == 0 || hdr->maxScroll < 0)
    return false;
  y = y * szo.cy / sz.cy;
  y = (y - hdr->top) * frame->height / (hdr->bottom - hdr->top) - scroll->barTop;
  int height = frame->height - scroll->barTop - scroll->barBottom;
  int content = frame->height - frame->insetTop - frame->insetBottom;
  int maxScroll = hdr->maxScroll + content;
  hdr->scrollPos = y * maxScroll / height;
  if (hdr->scrollPos < 0) hdr->scrollPos = 0;
  if (hdr->scrollPos > hdr->maxScroll) hdr->scrollPos = hdr->maxScroll;
  drawPreview();
  return true;
}

void OverlayPainter::updateReply()
{
  SharedData::Lock locker(&data);
  if (!locker.locked()) return;

  SharedHeader* hdr = data.header();
  FrameHeader* frame = data.frame();
  ReplyHeader* reply = data.reply();

  if (frame->width < params.padding * 3 + params.borderWidth * 2 ||
      frame->height < params.lineHeight + params.padding + params.borderWidth)
  {
    reply->width = 0;
    reply->height = 0;
    return;
  }

  reply->width = frame->width;
  reply->height = params.lineHeight + params.padding + params.borderWidth;

  HFONT hTextFont = FontSys::getFont(params.fontSize, params.fontFace);
  uint32 textColor = Image::clr_noflip(params.textColor, params.fgAlpha);

  int length;
  for (length = 0; length < 256 && hdr->replyText[length]; length++)
    hdr->replyPos[length] = FontSys::getTextSize(hTextFont, hdr->replyText, length).cx;
  if (length < 256)
    hdr->replyPos[length] = FontSys::getTextSize(hTextFont, hdr->replyText, length).cx;

  if (hdr->replyPos[hdr->replyCaret] - hdr->replyScroll < 0)
  {
    hdr->replyScroll = hdr->replyPos[hdr->replyCaret] - 10;
    if (hdr->replyScroll < 0) hdr->replyScroll = 0;
  }
  if (hdr->replyPos[hdr->replyCaret] - hdr->replyScroll > reply->width - 5)
    hdr->replyScroll = hdr->replyPos[hdr->replyCaret] - reply->width + 10;

  Image* text = renderText(hTextFont, hdr->replyText, hdr->replyPos[length], true, NULL);

  if (text && hdr->replySelStart != hdr->replyCaret)
  {
    int a = hdr->replyPos[hdr->replySelStart < hdr->replyCaret ? hdr->replySelStart : hdr->replyCaret];
    if (a > text->width()) a = text->width();
    int b = hdr->replyPos[hdr->replySelStart > hdr->replyCaret ? hdr->replySelStart : hdr->replyCaret];
    if (b > text->width()) b = text->width();
    uint32* bits = text->bits();
    for (int x = a; x < b; x++)
      for (int y = 0; y < text->height(); y++)
        bits[x + y * text->width()] = ((~bits[x + y * text->width()]) & 0xFF000000) | (params.sysColor & 0x00FFFFFF);
  }

  for (int i = 0; i <= length; i++)
    hdr->replyPos[i] += params.padding + params.borderWidth - hdr->replyScroll;

  Image image(reply->width, reply->height, data.bits(reply->offset));
  image.fill(0);

  if (text)
  {
    BLTInfo blt(text, params.padding + params.borderWidth - hdr->replyScroll, 1 + (reply->height - text->height() - 1) / 2);
    blt.modulate = textColor;
    image.blt(blt);
  }
  delete text;

  image.fill(params.borderColor, 0, 0, reply->width, 1);
  image.fill(params.borderColor, 0, 0, params.borderWidth, reply->height);
  image.fill(params.borderColor, reply->width - params.borderWidth, 0, params.borderWidth, reply->height);
  image.fill(params.borderColor, 0, reply->height - params.borderWidth, reply->width, params.borderWidth);

  reply->updated = 1;
}

void OverlayPainter::setMainWnd(MainWnd* wnd)
{
  if (data.lock())
  {
    data.header()->hWndLauncher = wnd->getHandle();
    data.unlock();
  }
  mainWnd = wnd;
}
void OverlayPainter::postReply()
{
  SharedData::Lock locker(&data);
  if (!locker.locked()) return;
  SharedHeader* hdr = data.header();

  if (hdr->replyText[0])
    client->send(String(hdr->replyText));

  hdr->replyText[0] = 0;
  hdr->replyCaret = hdr->replySelStart = hdr->replyScroll = 0;
  updateReply();
}
